<?php
/**
 * Created by PhpStorm.
 *
 * @Date: 2017-08-02
 * @Time: 15:51
 * @Author: cdkay
 * @Email: network@iyuanma.net
 *
 * @File： v163Class.php
 */

namespace library;

class v163Class{

    private static $instance = null;

    /**
     * @return static
     */
    public static function getInstance()
    {
        if (empty(self::$instance)) {
            self::$instance = new static(config('v163.appKey'), config('v163.appSecret'));
        }
        return self::$instance;
    }


    private $AppKey;                //开发者平台分配的AppKey
    private $AppSecret;             //开发者平台分配的AppSecret,可刷新
    private $Nonce;                 //随机数（最大长度128个字符）
    private $CurTime;               //当前UTC时间戳，从1970年1月1日0点0 分0 秒开始到现在的秒数(String)
    private $CheckSum;              //SHA1(AppSecret + Nonce + CurTime),三个参数拼接的字符串，进行SHA1哈希计算，转化成16进制字符(String，小写)
    const   HEX_DIGITS = "0123456789abcdef";
    private function __construct($AppKey,$AppSecret){
        $this->AppKey    = $AppKey;
        $this->AppSecret = $AppSecret;
    }
    public function getAppKey() {
        return $this->AppKey;
    }
    private function getNonce() {
        $nonce = '';
        for ($i=0; $i<128; $i++) {
            $nonce .= self::HEX_DIGITS[mt_rand(0, 15)];
        }
        return $nonce;
    }

    /**
     * 根据用户id生成对应的accid
     * uid和accid是一一对应的
     *
     * @param $uid
     * @return string
     */
    public static function generateAccId($uid) {
        return config('v163.accidPrefix').'_'.$uid;
    }
    public static function parseUidFromAccID($accid) {
        return substr($accid, strpos($accid,'_') + 1);
    }

    /**生成验证码**/
    public function checkSumBuilder(){
        //此部分生成随机字符串
        $hex_digits = self::HEX_DIGITS;
        $this->Nonce;
        for($i=0;$i<128;$i++){           //随机字符串最大128个字符，也可以小于该数
            $this->Nonce.= $hex_digits[rand(0,15)];
        }
        $this->CurTime = (string)(time());   //当前时间戳，以秒为单位

        $join_string = $this->AppSecret.$this->Nonce.$this->CurTime;
        $this->CheckSum = sha1($join_string);

    }

    /**
     * 检查视频直播回调签名
     *
     * @param $sign
     * @param $params
     * @return bool
     */
    public function verifyLiveCallbackSign($sign, $params) {

        ksort($params);

        $content = '';
        $i = 0;
        foreach ($params as $key => $val) {
            if ($i > 0) $content .= "&$key=$val";
            else $content .= "$key=$val";
            $i++;
        }

        $sign2 = md5($content.$this->getCallbackSignKey());

        return $sign === $sign2;
    }

    /**
     * 检查视频点播回调签名
     *
     * @param string $sign
     * @param string $data
     * @return bool
     */
    public function verifyVodCallbackSign($sign, $data) {

        $signKey = $this->getCallbackSignKey();

        return md5($data.$signKey) == $sign;
    }

    /**
     * 生成CheckSum签名
     * @return array
     */
    private function buildCheckSum() {
        $nonce = $this->getNonce();
        $curTime = strval(time());
        $checkSum = sha1($this->AppSecret.$nonce.$curTime);
        return [
            'AppKey:'.$this->AppKey,
            'Nonce:'.$nonce,
            'CurTime:'.$curTime,
            'CheckSum:'.$checkSum,
        ];
    }

    /**
     * 获取返回数据
     * @param $data
     * @return array
     * @throws \Exception
     */
    private function getRetData($data) {
        if (isset($data['code']) && 200 == $data['code']) {
            $ret = $data['ret'] ?? [];
            $ret['headers'] = $data['headers'];
            $ret['body'] = $data['body'];
            return $ret;
        } else {
            throw new \Exception($data['msg']??null, $data['code']);
        }
    }

    /*****post请求*****
     * @param $url
     * @param array $data
     * @return mixed
     */
    public function postDataCurl($url,$data=array()){
        if(!empty($data)){
            $json=json_encode($data);
        }else{
            $json="";
        }
        $timeout = 5000;
        $http_header = $this->buildCheckSum(); //发送请求前需先生成checkSum
        $http_header[] = 'Content-Type: application/json;charset=utf-8;';
        $http_header[] = 'Content-Length: ' . strlen($json);

        $ch = curl_init();
        curl_setopt ($ch, CURLOPT_URL, $url);
        curl_setopt ($ch, CURLOPT_POST, 1);
        curl_setopt ($ch, CURLOPT_POSTFIELDS, $json);
        curl_setopt ($ch, CURLOPT_HEADER, false);
        curl_setopt ($ch, CURLOPT_HTTPHEADER,$http_header);
        curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER,false);
        curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
        curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);

        $result = curl_exec($ch);
        if (false === $result) {
            $result =  curl_errno($ch);
        }
        curl_close($ch);
        $data = json_decode($result,true) ;
        $data['headers'] = $http_header;
        $data['body'] = $json;
        return $data;
    }

    /***频道添加**
     * @param $name
     * @param int $type
     * @return array
     */
    public function channel_add($name,$type=0){
        $url="https://vcloud.163.com/app/channel/create";
        $data=$this->postDataCurl($url,array("name"=>$name,"type"=>$type));
        return $this->getRetData($data);
    }
    /****频道更新*****/
    public function channel_update($name,$cid,$type=0){
        $url="https://vcloud.163.com/app/channel/update";
        return $data=$this->postDataCurl($url,array("name"=>$name,"cid"=>$cid,"type"=>$type));
    }
    /****频道删除******/
    public function channel_delete($cid){
        $url="https://vcloud.163.com/app/channel/delete";
        return $data=$this->postDataCurl($url,array("cid"=>$cid));
    }
    /**
     * 获取频道信息
     * @param $cid
     * @return array
     */
    public function channel_get($cid){
        $url = 'https://vcloud.163.com/app/channelstats';
        $data=$this->postDataCurl($url, ['cid' => $cid]);
        return $this->getRetData($data);
    }
    /**
     * 获取频道列表
     *
     * @param int $rows 单页记录数，默认值为10    否
     * @param int $page 要取第几页，默认值为1 否
     * @param string $oField 排序的域，支持的排序域为：ctime（默认）  否
     * @param int $sort 升序还是降序，1升序，0降序，默认为desc  否
     * @return array
     */
    public function channel_list($rows = 10, $page = 1, $oField = 'ctime', $sort = 1){
        $url = 'https://vcloud.163.com/app/channellist';
        $data=$this->postDataCurl($url, [
            'records' => $rows,
            'pnum' => $page,
            'ofield' => $oField,
            'sort' => $sort
        ]);
        return $this->getRetData($data);
    }
    public function channel_setCallback($callbackUrl) {
        $url = 'https://vcloud.163.com/app/chstatus/setcallback';
        $data = $this->postDataCurl($url, ['chStatusClk' => $callbackUrl]);
        $ret = $this->getRetData($data);
        return $ret['result'] ?? false;
    }
    /**重新获取推流地址***/
    public function channel_reset($cid){
        $url="https://vcloud.163.com/app/address";
        return $data=$this->postDataCurl($url,array("cid"=>$cid));
    }

    const CHANNEL_RECORD_OPEN = 1;
    const CHANNEL_RECORD_CLOSE = 0;
    /*****
     * 设置频道的录制状态
     *
     * @param string $cid       频道ID，32位字符串
     * @param int $needRecord   1-开启录制； 0-关闭录制
     * @param int $format 	    1-flv； 0-mp4
     * @param int $duration   	录制切片时长(分钟)，5~120分钟
     * @return mixed
     */
    public function channel_setRecord($cid, $needRecord = self::CHANNEL_RECORD_OPEN, $format = 0, $duration = 120){
        $url = 'https://vcloud.163.com/app/channel/setAlwaysRecord';
        $data = $data=$this->postDataCurl($url,[
            'cid' => $cid,
            'needRecord' => $needRecord,
            'format' => $format,
            'duration' => $duration,
        ]);
        return $this->getRetData($data);
    }
    /****暂停频道*****/
    public function channel_pause($cid){
        $url="https://vcloud.163.com/app/channel/pause";
        return $data=$this->postDataCurl($url,array("cid"=>$cid));
    }
    /****批量暂停频道****/
    public function channel_pauselist($cidList){
        $url="https://vcloud.163.com/app/channellist/pause";
        return $data=$this->postDataCurl($url,array("cidList"=>$cidList));
    }
    /****恢复频道*****/
    public function channel_resume($cid){
        $url="https://vcloud.163.com/app/channel/resume";
        return $data=$this->postDataCurl($url,array("cid"=>$cid));
    }
    /****批量恢复频道****/
    public function channel_resumelist($cidList){
        $url="https://vcloud.163.com/app/channellist/resume";
        return $data=$this->postDataCurl($url,array("cidList"=>$cidList));
    }
    /****获取频道的视频地址*****/
    public function channel_videolist($cid){
        $url="https://vcloud.163.com/app/videolist";
        return $data=$this->postDataCurl($url,array("cid"=>$cid));
    }

    /**
     * 获取时间段内的录制视频
     *
     * @param string $cid
     * @param int $beginTime 毫秒
     * @param int $endTime 毫秒
     * @return array
     */
    public function channel_vodVideoList($cid, $beginTime, $endTime) {
        $url = 'https://vcloud.163.com/app/vodvideolist';
        $data = $this->postDataCurl($url, ['cid'=>$cid,'beginTime'=>$beginTime,'endTime'=>$endTime]);
        return $this->getRetData($data)['videoList'];
    }

    /**
     * 发起合并录制视频的请求
     *
     * @param $outputName
     * @param array $vidList
     * @return array
     */
    public function channel_vodMerge($outputName, array $vidList) {
        $url = 'https://vcloud.163.com/app/video/merge';
        $data = $this->postDataCurl($url, ['outputName'=>$outputName,'vidList'=>$vidList]);
        return $this->getRetData($data);
    }

    /**
     * 录制重置：在直播录制过程中，结束正在进行的录制，开启一个新的录制任务。可用来对录制进行主动分片。
     *
     * @param $cid
     * @return array
     */
    public function channel_resetRecord($cid) {
        $url = 'https://vcloud.163.com/app/channel/resetRecord';
        $data = $this->postDataCurl($url,['cid'=>$cid]);
        return $this->getRetData($data);
    }

    /******
     视频点播接口
     *
     ****/

    /**
     * 上传签名
     *
     * @param $originFileName
     * @param $newFileName
     * @param $userDefInfo
     * @return mixed
     * @throws \Exception
     */
    public function vod_upload_init($originFileName, $newFileName, $userDefInfo) {
        $url = 'https://vcloud.163.com/app/vod/upload/init';

        $typeId = config('v163.videoTypeId'); // 在网易管理后台配置
        $presetId = config('v163.videoPresetId');
        $data = $this->postDataCurl($url, [
            'originFileName' => $originFileName,
            'userFileName' => $newFileName,
            'typeId' => $typeId,
            'presetId' => $presetId,
            'uploadCallbackUrl' => $this->getUploadCallUrl(),
            'callbackUrl' => $this->getTransCodingCallUrl(),
            'userDefInfo' => $userDefInfo,
        ]);

        if (200 == $data['code']) return $data['ret'];
        else throw new \Exception($data['msg'], $data['code']);
    }

    /**
     * 获取视频详情
     *
     * @param $vid
     * @return array
     */
    public function vod_get($vid) {
        $url = 'https://vcloud.163.com/app/vod/video/get';
        $data = $this->postDataCurl($url, ['vid' => $vid]);
        return $this->getRetData($data);
    }

    /**
     * 删除上传视频
     *
     * @param $vid
     * @return mixed
     */
    public function vod_del($vid) {
        $url = 'https://vcloud.163.com/app/vod/video/videoDelete';
        try{
            $data = $this->postDataCurl($url, ['vid' => $vid]);
            return $this->getRetData($data);
        }catch (\Exception $e){
            return false;
        }
    }

    const CALLBACK_SIGN_KEY = 'zq@163.com';
    private function getCallbackSignKey() {
        return md5(self::CALLBACK_SIGN_KEY);
    }

    const STYLE_ORIGIN = 0; // 源视频
    const STYLE_SD_MP4 = 1; // 流畅mp4
    const STYLE_HD_MP4 = 2; // 标清mp4
    const STYLE_SHD_MP4 = 3; // 高清mp4
    const STYLE_SD_FLV = 4; // 流畅flv
    const STYLE_HD_FLV = 5; // 标清flv
    const STYLE_SHD_FLV = 6; // 高清flv
    const STYLE_SD_HLS = 7; // 流畅hls
    const STYLE_HD_HLS = 8; // 标清hls
    const STYLE_SHD_HLS = 9; // 高清hls
    public static $SupportStyles = [
        self::STYLE_SD_FLV,
        self::STYLE_HD_FLV,
        self::STYLE_SHD_FLV,
    ];
    /**
     * @param $videoStyle
     * @param string $format
     * @return int
     */
    public static function getStyle($videoStyle, $format = 'flv') {
        if ('flv' == $format) {
            $videoStyle += 3;
        }
        if (in_array($videoStyle, self::$SupportStyles)) return $videoStyle;

        return 0;
    }

    /**
     * 创建加密视频
     *
     * @param $vid
     * @param $style
     * @return array
     */
    public function vod_encrypt_create($vid, $style) {
        $url = 'https://vcloud.163.com/app/vod/encrypt/create';
        $data = $this->postDataCurl($url, [
            'vid' => $vid,
            'style' => $style,
        ]);

        return $this->getRetData($data);
    }

    /**
     * 删除加密视频
     *
     * @param $encryptId
     * @return mixed
     */
    public function vod_encrypt_delete($encryptId) {
        $url = 'https://vcloud.163.com/app/vod/encrypt/delete';
        try{
            $data = $this->postDataCurl($url, [
                'encryptId' => $encryptId,
            ]);

            return $this->getRetData($data);
        }catch (\Exception $e){
            return false;
        }
    }

    /**
     * 获取加密视频的播放token
     *
     * @param $encryptId
     * @return null
     */
    public function vod_encrypt_token($encryptId) {
        $url = 'https://vcloud.163.com/app/vod/encrypt/getToken';
        $data = $this->postDataCurl($url, [
            'encryptId' => $encryptId,
            'expireTime' => config('v163.encryptExpired',60),
        ]);

        return $this->getRetData($data);
    }

    /**
     * 网易点播用户的添加
     *
     * @param $uid
     * @param null $token
     * @return array
     */
    public function vod_user_create($uid, $token = null) {
        $url = 'https://vcloud.163.com/app/vod/thirdpart/user/create';
        $data = $this->postDataCurl($url, [
            'accid' => self::generateAccId($uid),
            'type' => 1,
            'token' => $token,
        ]);

        return $this->getRetData($data);
    }

    /**
     * 网易点播用户的添加
     *
     * @param $uid
     * @param null $token
     * @return array
     */
    public function vod_user_update($uid, $token = null) {
        $url = 'https://vcloud.163.com/app/vod/thirdpart/user/update';
        $data = $this->postDataCurl($url, [
            'accid' => self::generateAccId($uid),
            'token' => $token,
        ]);

        return $this->getRetData($data);
    }

    /**
     * 网易点播用户删除
     * @param $uid
     * @return mixed
     */
    public function vod_user_delete($uid) {
        $url = 'https://vcloud.163.com/app/vod/thirdpart/user/userDelete';
        $data = $this->postDataCurl($url, [
            'accid' => self::generateAccId($uid),
        ]);

        try{
            return $this->getRetData($data);
        }catch (\Exception $e){
            return false;
        }
    }

    /*****
     用于配置网易参数的方法
     ****/
    /**
     * 设置直播录制回调的 Sign Key
     *
     * @return array
     */
    public function set_live_record_callback_signKey() {
        $url = 'https://vcloud.163.com/app/callback/setSignKey';
        $data = $this->postDataCurl($url, [
            'signKey' => $this->getCallbackSignKey(),
        ]);
        return $this->getRetData($data);
    }

    public function set_live_record_callback_url() {
        $url = 'https://vcloud.163.com/app/record/setcallback';
        $data = $this->postDataCurl($url, [
            'recordClk' => $this->getLiveRecordCallUrl(),
        ]);
        return $this->getRetData($data);
    }

    /**
     * 设置上传回调
     *
     * @return mixed
     * @throws \Exception
     */
    public function set_upload_callback() {
        $url = 'https://vcloud.163.com/app/vod/upload/setcallback';
        $data = $this->postDataCurl($url, [
            'callbackUrl' => $this->getUploadCallUrl(),
            'signKey' => $this->getCallbackSignKey(),
        ]);
        if (200 == $data['code']) return $data['ret'];
        else throw new \Exception($data['msg'], $data['code']);
    }

    /**
     * 设置转码回调
     *
     * @return mixed
     * @throws \Exception
     */
    public function set_transcode_callback() {
        $url = 'https://vcloud.163.com/app/vod/transcode/setcallback';
        $data = $this->postDataCurl($url, [
            'callbackUrl' => $this->getTransCodingCallUrl(),
            'signKey' => $this->getCallbackSignKey(),
        ]);
        if (200 == $data['code']) return $data['ret'];
        else throw new \Exception($data['msg'], $data['code']);
    }

    public function getUploadCallUrl() {
        return config('server.host') . '/callback/netease/upload_finish';
    }

    public function getTransCodingCallUrl() {
        return config('server.host') . '/callback/netease/transcode_finish';
    }

    public function getLiveRecordCallUrl() {
        return config('server.host') . '/callback/netease/live_record';
    }

}